package avriServer.client;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;

import avriServer.channel.Channel;
import avriServer.channel.WrongChannelNameException;
import avriServer.command.CommandBuffer;
import avriServer.json.JSONException;
import avriServer.json.JSONObject;
import avriServer.server.Dispatcher;
import avriServer.server.AvriServer;

public class Waiter extends Client {
	//TODO : remove BufferedReader occurrence of the waiter if it works : it's useless waiter never read...
	//or if it's necessary to keep the stream, think to close it and insert it into Client obj
	//private BufferedReader bufferedReader ;
	private long keepAlive = System.currentTimeMillis();
	private int cometType ;
	private String cometName ;
	private HashSet<Channel> suscribedChannels = new HashSet<Channel>();
	private String session ;
	private String publicId ;
	
	/**
	 * create a Waiter 
	 * @param _publicId
	 * @param _session
	 * @param _cometType
	 * @param _cometName
	 * @param _worker
	 * @throws UnvalidWaiterException if "publicId", "cometName" or "cometType" are null or empty.
	 */
	public Waiter(String _publicId, String _session, String _cometType, String _cometName, Worker _worker) throws UnvalidWaiterException {
		super(_worker.getSocket(), _worker.getPrintStream(), _worker.getBufferedReader());
		
		if (_publicId == null || _publicId.length() == 0)
			throw new UnvalidWaiterException("publicId is null");
		this.publicId = _publicId;
		
		if (_cometName == null || _cometName.length() == 0)
			throw new UnvalidWaiterException("cometName is empty  or unexistant");
		this.cometName = _cometName ;
		
		try {
			this.cometType = Integer.parseInt(_cometType) ;
		} catch (Exception e ) {
			throw new UnvalidWaiterException("Unvalid cometType. Must be a String wich represents an int (e.g: \"1\"");
		}
		this.session = _session;
		//send the header.
    	this.sendCometHeader(this.cometType);    	
    	//set the session.
		this.push("avriServer.setSessionAndPublicId", this.session, this.publicId);
	}
	/**
	 * Automatically create the Waiter.
	 * @param _attributes 
	 * @param _attributes HAVE TO contains attributes "publicId", "cometName", "cometType"
	 * @param _worker
	 * @throws UnvalidWaiterException if "publicId", "cometName" or "cometType" are null or empty.
	 * @throws JSONException 
	 */	
	public Waiter(HashMap<String, String> _attributes, JSONObject _commandAttributes, Worker _worker) throws UnvalidWaiterException, JSONException {
		this(_attributes.get("publicId"), Dispatcher.generateWaiterSession(), _attributes.get("cometType"), _attributes.get("cometName"), _worker);
	}


	/**
	 * Push a data to the browser. The data will be catch by the javascript object.
	 * push method is synchronized to avoid multithread error.
	 * @param _stringToBePush
	 */
	private synchronized void pushJson (String _stringToBePush) {
		super.push(_stringToBePush, this.cometType, this.cometName);
	}

	public void push (CommandBuffer _commandBuffer) {
		this.pushJson(_commandBuffer.getCommandsAsPartOfArray());
	}
	/**
	 * add the Channel _channel to the list of the channel the Waiter has subscribe.
	 * @param _channel the channel you want to subscribe
	 */
	public void subscribe (Channel _channel) {
		this.suscribedChannels.add(_channel) ;
	}
	
	/**
	 * remove the Channel _channel to the list of the channel the Waiter has subscribe.
	 * Also remove  the current waiter from the channel _channel's waiters hashSet
	 * @param _channel the channel you want to subscribe
	 */
	public void unsubscribe (Channel _channel) {
		_channel.removeWaiter(this);
		this.suscribedChannels.remove(_channel);
	}
	/**
	 * Keep the waiter alive.
	 * It updates the keepAlive attribute to the current time.
	 */
	public void keepAlive() {
		this.keepAlive = System.currentTimeMillis() ;
	}
	/**
	 * @return true is the waiter is keptAlive. False if the waiter has been inactive more than Server.KEEPALIVE seconds
	 */
	public boolean isKeptAlive () {
		return (System.currentTimeMillis() - this.keepAlive < Dispatcher.getServer().getKeepAlive());
	}
	
	/**
	 * @return the waiter identifier
	 */
	public String getSession() {
		return this.session;
	}

	/**
	 * @return the waiter publicId.
	 * publicId is used in the HashMap has the key value !
	 */
	public String getPublicId() {
		return this.publicId;
	}
	/**
	 * @return the list of channels the waiter has subscribed.
	 */
	public HashSet<Channel> getSuscribedChannels () {
		return this.suscribedChannels;
	}
	/**
	 * Push the function _functionName with its attributes _attributes to all the user of all the channels
	 * e.g: 
	 * <code>
	 * 		Dispatcher.push("error", "message1", "foo") will push the message {functionName: "error", arguments: ["message1", "foo"]}
	 * 		//Javascript api will automatically launch the function : avriServer.functions.error ("message1", "foo") ;
	 * </code>		
	 * Also work with Dispatcher.push("myJavascriptSpaceName.error", "message1", "foo") : avriServer.functions.myJavascriptSpaceName.error ("message1", "foo")
	 * @param _channel
	 * @param _message
	 * @throws JSONException 
	 */
	public void push(String _functionName, Object... _functionArguments){
		this.pushJson(CommandBuffer.getCommand(_functionName, _functionArguments)) ;
	}

	
	/**
	 * unsubscribe the waiter from all the channels he was
	 */
	public void unsubscribeFromAllChannel () {
		for (Channel channel : this.suscribedChannels) {
			channel.removeWaiter(this);
		}
	}
	/**
	*Method called when the dispatcher disconnect a waiter.
	*Should be override if you want a specific action.
	*/
	
	public void actionExecutedBeforedisconnection () {		
		this.push("avriServer.disconnect", "you have been disconnected from the server");
	}
	
	/**
	 * @return the collection of root subscribed Channel.<br />
	 * e.g: subscribedChannels = ["general/channel1/myChannel", "general/channel1/myOtherChannel", "general/channel1", "toto"]<br /> 
	 * <code>
	 *   return ["general/channel1", "toto"]
	 * </code>
	 */
	public Collection<Channel> getRootChannelSubscribed () {
		Collection<Channel> rootChannel = new ArrayList<Channel>();
		rootChannel.addAll(this.suscribedChannels);
		StringBuffer channelName = new StringBuffer () ;
		int i ;
		for (Channel c : this.suscribedChannels) {
			String[] subChannels = c.getAbsoluteName().split("/");
			for (i = 0; i<subChannels.length; i++) {
				if (i>0)	
					channelName.append("/");
				channelName.append(subChannels[i]);
				try {
					if (rootChannel.contains(new Channel(channelName.toString()))) {
						rootChannel.remove(c);
					}
				} catch (WrongChannelNameException e) {
					e.printStackTrace();
				}
			}
		}
		return rootChannel ;
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((publicId == null) ? 0 : publicId.hashCode());
		return result;
	}
	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Waiter other = (Waiter) obj;
		if (publicId == null) {
			if (other.publicId != null)
				return false;
		} else if (!publicId.equals(other.publicId))
			return false;
		return true;
	}


	
}
